﻿// Copyright (C) 2013-2014 Ralph Becket
// Released under a variant of the Microsoft Public License -- see ../LICENCE.txt.
//
// This file extends Rawb with functions to asynchronously load JavaScript and HTML.

var rawb = rawb || {};

(function () {

    // ---- API ----

    /*
    
!! rawb.load(url, handler)

Asynchronously "get" the data from _url_.
If there is a problem, report an error via an alert and abandon all further work.
Otherwise, run the _handler_ on the response.
After the _handler_, run any queued _runAfterLoading_ continuations if there
are no outstanding loads in progress.

*Parameters*

*url:* _string_
Required.  Must not be an observable.
The URL to load.

*handler:* _function (any)_
Required.  Must not be an observable.
The action invoked on the XMLHttpRequest response.

    */
    rawb.load = function (url, handler) {
        if (inError) return;
        numPendingLoads++;
        var xhr = new XMLHttpRequest();
        xhr.open("get", url);
        xhr.onreadystatechange = handlerWrapper(xhr, url, handler);
        xhr.send();
    };

    /*

!! rawb.loadAndAddHtml(url)

Asynchronously load HTML from the given URL and append it to the
document body (if the response consists of several HTML elements,
they are appended to the document body in order).
If there is a problem, report an error via an alert and abandon all further work.

*Parameters*

*url:* _string_
Required.  Must not be an observable.
The URL to load.

    */
    rawb.loadAndAddHtml = function (url) { rawb.load(url, addHtml); };

    /*

!! rawb.loadAndRunScript(url)

Asynchronously load Javascript from the given URL and run it
in the global scope using _eval_.
If there is a problem, report an error via an alert and abandon all further work.

*Parameters*

*url:* _string_
Required.  Must not be an observable.
The URL to load.

    */
    rawb.loadAndRunScript = function (url) { rawb.load(url, runScript); };

    /*

!! rawb.runAfterLoading(continuation)

Add a function to be called once all outstanding loads have completed.
Continuations are called in the order they are added.
Continuations are run at most once.
If there are no outstanding loads, the continuation is run immediately.
If a loading error has occurred, no continuations will be run.

*Parameters*

*continuation:* _function ()_
Required.  Must not be an observable.
The action to carry out once all outstanding loads have completed.

    */

    rawb.runAfterLoading = function (continuation) {
        var ol = onLoaded;
        onLoaded = function () { ol(); continuation(); };
        runOnLoadedIfReady();
    };

    // ---- Implementation detail. ----

    var numPendingLoads = 0;

    var inError = false;

    var runScript = function (script) {
        eval.call(null, script); // Null here means "at the top level".
    };

    var addHtml = function (html) {
        var div = document.createElement("div");
        div.innerHTML = html;
        var elts = div.childNodes;
        for (var i = 0; i < elts.length; i++) {
            document.body.appendChild(elts[i]);
        }
    };

    var noop = function () { };

    var onLoaded = noop;

    var runOnLoadedIfReady = function () {
        if (numPendingLoads !== 0) return; // Now is not the time.
        if (inError) return; // It will never be the time!
        var ol = onLoaded;
        onLoaded = noop; // Ensure we call each continuation at most once.
        ol();
    }

    var handlerWrapper = function (xhr, url, handler) {
        return function () {
            if (inError) return; // Game over already.
            if (xhr.readyState != 4) return; // The load has not completed.
            numPendingLoads--;
            if (xhr.status !== 200) {
                inError = true;
                alert("An error occurred loading\n" + url);
            } else {
                handler(xhr.response);
                runOnLoadedIfReady();
            };
        }
    };

})();
